package cn.deng.mybatis.builder.xml;


/**
 * @author: DengJingWen deng
 * @description: XML配置构建器，建造者模式，继承BaseBuilder
 * @create: 2025-05-16
 */
import cn.deng.mybatis.io.Resources;
import cn.deng.mybatis.mapping.MappedStatement;
import cn.deng.mybatis.mapping.SqlCommandType;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;

import cn.deng.mybatis.builder.BaseBuilder;
import cn.deng.mybatis.session.Configuration;

import java.io.Reader;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * XMLConfigBuilder
 * 是 XML 配置构建器，它负责读取 MyBatis 的主配置文件（通常是 mybatis-config.xml）和
 * 所有映射文件（Mapper.xml），然后将这些配置信息转换成 Java 对象，最终构建出一个完整的 Configuration 对象。
 */
public class XMLConfigBuilder extends BaseBuilder {

    private Element root;

    public XMLConfigBuilder(Reader reader) {
        // 1. 调用父类初始化Configuration
        super(new Configuration());
        // 2. dom4j 处理 xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new InputSource(reader));
            root = document.getRootElement();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析配置；类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器
     *
     * @return Configuration
     */
    public Configuration parse() {
        try {
            // 解析映射器
            mapperElement(root.element("mappers"));
        } catch (Exception e) {
            throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
        return configuration;
    }

    /**
     *
     * 一、获取所有<mapper>节点、
     * 二、读取每个节点对应的语句xml文件，
     * 三、从每个xml文件解析出具体的 sql 语句，参数用? 代替
     * 四、构建每个语句的映射语句类 MappedStatement
     * 五、缓存到配置类 Configuration 的 mappedStatements 中，键为：namespace + 方法名
     * 六、通过 <mapper>节点的 namespace，即 DAO 接口名，执行 addMapper 方法
     *      通过代理工厂 MapperProxyFactory 生成 Mapper（DAO） 接口代理对象，
     *      再缓存注册到 Configuration 的 MapperRegistry属性对象的 knownMappers 中。
     * @param mappers
     * @throws Exception
     */
    private void mapperElement(Element mappers) throws Exception {
        // 遍历所有<mapper>节点
        List<Element> mapperList = mappers.elements("mapper");
        for (Element e : mapperList) {
            // 通过指定属性，获取mapper文件路径
            String resource = e.attributeValue("resource");
            // 根据获取的mapper文件路径，加载mapper文件，获取对应Reader对象
            Reader reader = Resources.getResourceAsReader(resource);
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(new InputSource(reader));

            // 获取到xml文件的根节点
            Element root = document.getRootElement();
            //命名空间
            String namespace = root.attributeValue("namespace");

            // 生成每个 SELECT 查询语句的 映射语句类 mappedStatement
            // 获取全部的Select节点，分别处理SELECT语句
            List<Element> selectNodes = root.elements("select");
            for (Element node : selectNodes) {
                // 获取SQL属性
                String id = node.attributeValue("id");
                String parameterType = node.attributeValue("parameterType");
                String resultType = node.attributeValue("resultType");
                String sql = node.getText();

                // 处理参数占位符(#{}替换为?)
                Map<Integer, String> parameter = new HashMap<>();

                // 生成一个能匹配 #{xxx} 字符串格式的正则表达式模式
                Pattern pattern = Pattern.compile("(#\\{(.*?)})");

                // 创建 Matcher 对象用于查找匹配内容
                Matcher matcher = pattern.matcher(sql);

                // 将 #{}/&{} 字符串转换为 ?
                // find()：用于在目标字符串中查找是否有下一个符合正则表达式的子串。
                // 如果找到匹配的内容，返回 true，并将内部指针移动到下一个位置；否则返回 false。
                //
                // 每次调用 find()，它会从上次匹配结束的位置开始，继续查找下一个匹配项。
                // 它是一个 游标式查找 的过程，内部维护了一个指针位置。
                // 如果找到匹配项，返回 true，并将当前匹配的内容记录下来（包括分组）。
                // 如果没找到更多匹配项，返回 false，循环终止。
                for (int i = 1; matcher.find(); i++) {
                    // 第一个括号内容 #{id}
                    String g1 = matcher.group(1);
                    // 第二个括号内容({}括号) id
                    String g2 = matcher.group(2);
                    parameter.put(i, g2);
                    sql = sql.replace(g1, "?");
                }
                // 构建并注册MappedStatement
                String msId = namespace + "." + id;
                String nodeName = node.getName();
                // 切换为大写，并转换为 SqlCommandType 枚举
                SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
                // 生成映射语句类
                MappedStatement mappedStatement = new MappedStatement.Builder(configuration, msId, sqlCommandType, parameterType, resultType, sql, parameter).build();
                // 添加解析 SQL
                configuration.addMappedStatement(mappedStatement);
            }

            // 注册Mapper映射器
            configuration.addMapper(Resources.classForName(namespace));
        }
    }

}

